package adventure.commands.skills;

import adventure.Adventure;
import adventure.AdventureEdit;
import adventure.commands.AbstractCommand;
import adventure.placeable.Fightable;
import adventure.placeable.PlaceableManager;
import colour.Colour;
import colour.ColourUtil;
import java.util.Random;
import player.Player;
import player.PlayerEdit;

/**
 *
 * @author Michael Hanns
 *
 */
public abstract class AbstractSkill extends AbstractCommand implements Skill, SkillEdit, Cloneable {

    // Skill ID number
    private int id;
    // Skill description
    protected String description;
    // Skill usage description
    protected String usage;
    //
    // Skill category
    protected String category;
    //
    // Level skill learned at
    protected int levelLearned;
    // Special required
    protected int specialReq;
    // Cooldown (seconds)
    protected int coolDown;
    protected long lastUsed;
    // Can't be used in combat
    protected boolean requiresConcentration;
    // Difficulty rating that must be rolled for success
    protected int difficulty;
    // Number of dice to roll
    protected int dice;
    // Number of sides to dice
    protected int diceSides;
    // Influencing stat
    protected int mainStat;
    //
    // Placeables Manager
    private PlaceableManager placeables;
    //
    // Items needed/consumed when using - X axis is Item ID, Y axis is quantity
    protected int[][] itemsNeeded;
    //
    // Stat flags
    public final int ATK = 0;
    public final int DEF = 1;
    public final int INTEL = 2;
    public final int SPD = 3;
    public final int LCK = 4;
    // Random object for dice rolling
    protected Random r;
    //
    // Attack flags differentiate the different forms of attack description
    public final int YOU = 0;
    public final int YOUR = 1;
    public final int CUSTOM = 2;
    // "You <action> <target>!" action player sees
    protected int actionFlag;
    // Action other players see
    protected String actionDesc;
    // Decimal formatter (mostly for cooldown output)
    protected java.text.DecimalFormat format = new java.text.DecimalFormat("#.##");

    public AbstractSkill(Adventure a, String[] inputSynonyms, int id) {
        super(a, inputSynonyms);
        this.id = id;


        usage = inputSynonyms[0] + " <target>";
        for (int x = 1; x < inputSynonyms.length; x++) {
            usage += ", " + inputSynonyms[x] + " <target>";
        }

        description = "";
        category = "";

        r = new Random();
        levelLearned = 1;
        specialReq = 0;
        coolDown = 5;
        lastUsed = System.currentTimeMillis();
        requiresConcentration = false;
        difficulty = 1;
        dice = 1;
        diceSides = 4;
        mainStat = ATK;

        actionFlag = YOUR;
        actionDesc = "attack damages";

        itemsNeeded = new int[0][2];

        placeables = (PlaceableManager) ((AdventureEdit) adv).getPlaceableManager();
    }

    @Override
    public void setID(int id) {
        this.id = id;
    }

    @Override
    public void setSynonyms(String[] syns) {
        this.synonyms = syns;

        for (int x = 0; x < synonyms.length; x++) {
            synonyms[x] = synonyms[x].toUpperCase();
        }

        usage = synonyms[0] + " <target>";
        for (int x = 1; x < synonyms.length; x++) {
            usage += ", " + synonyms[x] + " <target>";
        }
    }

    @Override
    public void setDescription(String desc) {
        this.description = desc;
    }
    
    @Override
    public void setCategory(String cat) {
        this.category = cat;
    }

    @Override
    public void setLevelToLearn(int level) {
        this.levelLearned = level;
    }

    @Override
    public void setSpecialReq(int sp) {
        this.specialReq = sp;
    }

    @Override
    public void setCooldown(int sec) {
        this.coolDown = sec;
    }
    
    @Override
    public void setRequiresConcentration(boolean rc) {
        this.requiresConcentration = rc;
    }
    
    @Override
    public void setDifficulty(int diff) {
        this.difficulty = diff;
    }

    @Override
    public void setDice(int num, int sides) {
        this.dice = num;
        this.diceSides = sides;
    }

    @Override
    public void setMainStat(int statFlag) {
        this.mainStat = statFlag;
    }

    @Override
    public void setActionFlag(int atkFlag) {
        this.actionFlag = atkFlag;
    }

    @Override
    public void setAttackDesc(String desc) {
        this.actionDesc = desc;
    }

    @Override
    public void setItemsNeeded(int[][] items) {
        this.itemsNeeded = items;
    }

    @Override
    public int getID() {
        return id;
    }

    @Override
    public String[] getSynonyms() {
        return synonyms;
    }

    @Override
    public String getName() {
        return synonyms[0];
    }

    @Override
    public int levelLearned() {
        return levelLearned;
    }

    @Override
    public String getDescription() {
        return description;
    }
    
    @Override
    public String getCategory() {
        return category;
    }

    @Override
    public abstract String getType();

    @Override
    public abstract Colour getTypeColour();

    @Override
    public String getUsage() {
        return usage;
    }

    @Override
    public int getSpecialReq() {
        return specialReq;
    }

    @Override
    public int getCooldown() {
        return coolDown;
    }
    
    @Override
    public boolean requiresConcentration() {
        return requiresConcentration;
    }
    
    @Override
    public int getDifficulty() {
        return difficulty;
    }

    @Override
    public boolean isDicePerLevel() {
        return dice <= 0;
    }

    @Override
    public int getNumDice() {
        return dice;
    }

    @Override
    public int getDiceSides() {
        return diceSides;
    }

    @Override
    public int getMainStat() {
        return mainStat;
    }

    @Override
    public int getAttackFlag() {
        return actionFlag;
    }

    @Override
    public String getAttackDesc() {
        return actionDesc;
    }

    @Override
    public int[][] getItemsNeeded() {
        return itemsNeeded;
    }

    @Override
    public String validateAndExecute(Player player, String[] args) {
        if (((AdventureEdit) adv).getRoomManager().isLightPresent(
                player.x(), player.y(), player.z())) {
            double cooldownRemaining = ((double) ((double) lastUsed - System.currentTimeMillis() + (double) coolDown * 1000) / 1000);
            if (lastUsed > System.currentTimeMillis() - coolDown * 1000) {
                return "You must wait "
                        + ColourUtil.colourise(format.format(cooldownRemaining), ColourUtil.white)
                        + " seconds to use that skill again!";
            }

            // Need special to use
            if (player.getSpecial() < specialReq) {
                return "You do not have enough special to do that!";
            }

            // Needs items to use
            for (int i = 0; i < itemsNeeded.length; i++) {
                if (!player.hasItemUses(itemsNeeded[i][0], itemsNeeded[i][1])) {
                    return "You do not have enough " + ColourUtil.colourise(placeables.getItem(itemsNeeded[i][0]).getName(), ColourUtil.item)
                            + " to do that!";
                }
            }
            
            // Can't be used in combat
            if (requiresConcentration && player.inCombat()) {
                return "You cannot do that while in combat!";
            }

            return performSkill(player, args);
        } else {
            return "You cannot see anything in the darkness!";
        }
    }

    protected abstract String performSkill(Player player, String[] args);
    
    // Returns a boolean array of length 2 with values Success and Critical
    //
    // Success is determined by rolling a 100 sided dice and adding the
    // players most influential stat (as determined by mainStat) multiplied
    // by a percentage determined by the players Luck stat (unless the main stat
    // for the skill IS Luck, in which case this it's just by the Luck stat).
    // If the roll is above the 'Difficulty' rating, then it's a success.
    //
    // A roll of > (100 - mainStat * (Luck*.01)) means the skill is a critical success.
    // 
    protected boolean[] skillSuccess(Player player) {
        int hitRoll = (int) r.nextDouble()*100;
        
        int hitBonus;
        if(mainStat != 4) {
            hitBonus = (int) (getMainStatVal((Fightable)player)*(1 + player.getLuck()/100));
        } else {
            hitBonus = (int) getMainStatVal((Fightable)player);
        }
        
        boolean success = hitRoll + hitBonus >= difficulty;
        
        if(!success) {
            return new boolean[] {false, false};
        }
        
        boolean critical = hitRoll + hitBonus >= 100;
        
        return new boolean[] {true, critical};
    }

    protected void consumePrerequisites(Player player) {
        lastUsed = System.currentTimeMillis();
        ((PlayerEdit) player).setSpecial(player.getSpecial() - specialReq);
        
        for (int i = 0; i < itemsNeeded.length; i++) {
            if (placeables.getItem(itemsNeeded[i][0]).isConsumable()) {
                for(int c = 0; c < itemsNeeded[i][1]; c++) {
                    player.useItem(itemsNeeded[i][0]);
                }
            }
        }
    }

    protected int getMainStatVal(Fightable subject) {
        if (mainStat == ATK) {
            return subject.getAttack();
        } else if (mainStat == DEF) {
            return subject.getDefence();
        } else if (mainStat == INTEL) {
            return subject.getIntelligence();
        } else if (mainStat == SPD) {
            return subject.getSpeed();
        } else if (mainStat == LCK) {
            return subject.getLuck();
        }
        return 0;
    }

    protected String getMainStatName() {
        if (mainStat == ATK) {
            return "ATK";
        } else if (mainStat == DEF) {
            return "DEF";
        } else if (mainStat == INTEL) {
            return "INT";
        } else if (mainStat == SPD) {
            return "SPD";
        } else if (mainStat == LCK) {
            return "LCK";
        }
        return "";
    }

    @Override
    public Skill cloneThis() {
        Skill newClone = null;
        try {
            newClone = (AbstractSkill) this.clone();
            newClone.setPlayerManager(players);
            newClone.setBroadcaster(out);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return newClone;
    }

    @Override
    public Object clone() {
        try {
            Object clone = super.clone();
            return clone;
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
}
